package edu.unika.aifb.graphindex.algorithm.largercp;

/**
 * Copyright (C) 2009 Lei Zhang (beyondlei at gmail.com)
 * 
 * This file is part of the graphindex project.
 *
 * graphindex is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 2
 * as published by the Free Software Foundation.
 * 
 * graphindex is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with graphindex.  If not, see <http://www.gnu.org/licenses/>.
 */

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.apache.log4j.Logger;

import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentLockedException;

import edu.unika.aifb.graphindex.index.DataIndex;
import edu.unika.aifb.graphindex.index.DataIndex.NodeListener;
import edu.unika.aifb.graphindex.storage.StorageException;
import edu.unika.aifb.graphindex.util.Util;

public class LargeRCP {
	private BlockCache m_bc;
	private boolean m_ignoreDataValues = false;
	private Set<String> m_allEdges;
	private Set<String> m_forwardEdges, m_backwardEdges;
	private DataIndex m_gs;
	private Environment m_env;
	private static final Logger log = Logger.getLogger(LargeRCP.class);
	
	public LargeRCP(DataIndex gs, Environment env, Set<String> fw, Set<String> bw, Set<String> allEdges) throws EnvironmentLockedException, DatabaseException {
		m_forwardEdges = fw;
		m_backwardEdges = bw;
		m_allEdges = allEdges;
		m_allEdges.addAll(m_forwardEdges);
		m_allEdges.addAll(m_backwardEdges);
		
		m_gs = gs;
		m_env = env;

		m_bc = new BlockCache(m_env);
	}
	
	public void setIgnoreDataValues(boolean ignore) {
		m_ignoreDataValues = ignore;
	}

	private void refinePartitionSimple(List<Block> blocks, Collection<String> nodes) {
		// TODO check if nodes should be moved multiple times in one refinement step, on subsequent properties
		List<Block> splitBlocks = new LinkedList<Block>();
		m_bc.setNodeCacheActive(true);
		for (String node : nodes) {
			Block block = m_bc.getBlock(node);
			Block splitBlock = block.getSplitBlock();
			if (splitBlock == null) {
				splitBlock = m_bc.createBlock();
				block.setSplitBlock(splitBlock);
				splitBlocks.add(block);
			}

			splitBlock.add(node);
		}
		
		for (Block block : splitBlocks) {
			Block splitBlock = block.getSplitBlock();
			blocks.add(splitBlock);
			block.setSplitBlock(null);
			if (block.size() == splitBlock.size()) {
				m_bc.removeBlock(block);
				blocks.remove(block);
			}
			else {
				block.setSize(block.size() - splitBlock.size());
				m_bc.removeNodes(block, m_bc.getNodes(splitBlock));
			}
		}
		m_bc.setNodeCacheActive(false);
	}

	
	private void createPartitionSimple(List<Block> blocks, List<String> edges, int pathLength, boolean forward) throws StorageException, IOException {
		int steps = 0;
		
		log.debug("forward: " + forward);
		log.debug("blocks size: " + blocks.size());
		
		if (blocks.size() == 0) {
			final Block b = m_bc.createBlock();
			blocks.add(b);

			// start
			// init block cache, set one block for all nodes; this block will be "empty", i.e. the blockDb won't contain
			// the nodes, which will be fixed after the first splitting
			for (String property : m_allEdges)
				m_gs.iterateNodes(property, new NodeListener() {
					public void node(String node) {
						m_bc.setBlock(node, b);
					}
				});
			
			b.setSize((int)m_bc.getNodeCount());
		}
		
		while (steps < pathLength) {
			int blockCount = blocks.size();
			
			for (String property : edges) {
				log.debug("property " + property);
				Set<String> image = null;
				if(forward)
					image = m_gs.getSubjectNodes(property);
				else
					image = m_gs.getObjectNodes(property); 
				refinePartitionSimple(blocks, image);
			}
			
			steps++;
			log.debug("steps: " + steps + ", blocks: " + blocks.size());
			log.debug("block set : " + blocks);
			if (blockCount == blocks.size()) {
				log.debug("no split, done");
				break;
			}
			
			System.gc();
			log.debug(Util.memory());
		}	
	}
		
	public void createIndexGraph(int pathLength) throws StorageException, IOException, InterruptedException, DatabaseException {
		log.debug("ignore data values: " + m_ignoreDataValues);
		log.debug("---------------------------------- starting backward bisim");
		log.debug("backward edges: " + m_backwardEdges.size());
	
		Block.m_bc = m_bc;
		
		System.gc();
		
		List<Block> blocks = new ArrayList<Block>();


		List<String> properties = new ArrayList<String>(m_backwardEdges);
		Collections.sort(properties);
		if (properties.size() > 0)
			createPartitionSimple(blocks, properties, pathLength, false);
		
		System.gc();

		log.debug("---------------------------------- starting forward bisim");
		log.debug("forward edges: " + m_forwardEdges.size());
		log.debug(Util.memory());
		
		properties = new ArrayList<String>(m_forwardEdges);
		Collections.sort(properties);
		if (properties.size() > 0)
			createPartitionSimple(blocks, properties, pathLength, true);
		
		m_bc.close();
	}
}
